﻿using System.Collections.Generic;
using SpiceNet.Circuits;
using SpiceNet.Diagnostics;
using System.Numerics;
using SpiceNet.Parameters;
using SpiceNet.Simulations;

namespace SpiceNet.Models
{
    /// <summary>
    /// An instance of an inductor
    /// </summary>
    public class Inductor : Instance
    {
        /// <summary>
        /// Get parameters
        /// </summary>
        public override Dictionary<string, IP> ParamTable => _pTable;

        /// <summary>
        /// Private variables
        /// </summary>
        private int INDposNode, INDnegNode, INDbrEq;
        private int INDstate;

        /// <summary>
        /// Gets the inductor current equation index - Needed for mutual inductances
        /// </summary>
        public int INDibr => INDbrEq;

        /// <summary>
        /// Gets the inductor flux state - Needed for mutual inductances
        /// </summary>
        public int FluxState => INDstate + INDflux;

        #region Parameters
        private enum _c
        {
            /* device parameters */
            IND_IND = 1,
            IND_IC = 2,
            IND_FLUX = 3,
            IND_VOLT = 4,
            IND_IND_SENS = 5,
            IND_CURRENT = 6,
            IND_POWER = 7,

            /* device questions */
            IND_QUEST_SENS_REAL = 201,
            IND_QUEST_SENS_IMAG = 202,
            IND_QUEST_SENS_MAG = 203,
            IND_QUEST_SENS_PH = 204,
            IND_QUEST_SENS_CPLX = 205,
            IND_QUEST_SENS_DC = 206
        }
        private static Dictionary<string, IP> _pTable = new Dictionary<string, IP>
        {
            { "inductance", new IP(IF.IOPAP, (int)_c.IND_IND, T.REAL, "Inductance of inductor") },
            { "ic", new IP(IF.IOPAU, (int)_c.IND_IC, T.REAL, "Initial current through inductor") },
            { "sens_ind", new IP(IF.IP, (int)_c.IND_IND_SENS, T.FLAG, "flag to request sensitivity WRT inductance") },
            { "flux", new IP(IF.OP, (int)_c.IND_FLUX, T.REAL, "Flux through inductor") },
            { "v", new IP(IF.OP, (int)_c.IND_VOLT, T.REAL, "Terminal voltage of inductor") },
            { "volt", new IP(IF.OPR, (int)_c.IND_VOLT, T.REAL, "") },
            { "i", new IP(IF.OP, (int)_c.IND_CURRENT, T.REAL, "Current through the inductor") },
            { "current", new IP(IF.OPR, (int)_c.IND_CURRENT, T.REAL, "") },
            { "p", new IP(IF.OP, (int)_c.IND_POWER, T.REAL, "instantaneous power dissipated by the inductor") },
            { "sens_dc", new IP(IF.OPU, (int)_c.IND_QUEST_SENS_DC, T.REAL, "dc sensitivity sensitivity") },
            { "sens_real", new IP(IF.OPU, (int)_c.IND_QUEST_SENS_REAL, T.REAL, "real part of ac sensitivity") },
            { "sens_imag", new IP(IF.OPU, (int)_c.IND_QUEST_SENS_IMAG, T.REAL, "dc sensitivity and imag part of ac sensitivty") },
            { "sens_mag", new IP(IF.OPU, (int)_c.IND_QUEST_SENS_MAG, T.REAL, "sensitivity of AC magnitude") },
            { "sens_ph", new IP(IF.OPU, (int)_c.IND_QUEST_SENS_PH, T.REAL, "sensitivity of AC phase") },
            { "sens_cplx", new IP(IF.OPU, (int)_c.IND_QUEST_SENS_CPLX, T.COMPLEX, "ac sensitivity") }
        };

        /// <summary>
        /// The inductance
        /// </summary>
        public Parameter<double> INDinduct { get; } = new Parameter<double>();
        public Parameter<double> INDinitCond { get; } = new Parameter<double>();
        public int INDsenParmNo { get; set; }
        #endregion

        #region Constants
        private const int INDflux = 0;    /* flux in the inductor */
        private const int INDvolt = 1;  /* voltage - save an entry in table */
        private const int INDsensxp = 2; /* charge sensitivities and their derivatives.
                                +3 for the derivatives - pointer to the
                beginning of the array */
        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name"></param>
        public Inductor(string name) : base(name, 2, typeof(InductorModel)) { }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">The name of the inductor</param>
        /// <param name="posnode">The name of the positive node</param>
        /// <param name="negnode">The name of the negative node</param>
        /// <param name="inductance">The inductance (H)</param>
        public Inductor(string name, string posnode, string negnode, double inductance) : this(name)
        {
            Connect(posnode, negnode);
            INDinduct.Par(inductance);
        }

        /// <summary>
        /// Set parameter
        /// </summary>
        protected override void Param(int id, object value, Circuit ckt = null)
        {
            switch ((_c)id)
            {
                case _c.IND_IND:
                    INDinduct.Par((double)value);
                    break;
                case _c.IND_IC:
                    INDinitCond.Par((double)value);
                    break;
                case _c.IND_IND_SENS:
                    INDsenParmNo = (int)value;
                    break;
                default:
                    throw new BadParameterException();
            }
        }

        /// <summary>
        /// Ask parameter
        /// </summary>
        protected override object Ask(int id, Circuit ckt = null)
        {
            switch ((_c)id)
            {
                case _c.IND_FLUX:
                    return ckt.State.States[0][INDstate + INDflux];
                case _c.IND_VOLT:
                    return ckt.State.States[0][INDstate + INDvolt];
                case _c.IND_IND:
                    return INDinduct.Value;
                case _c.IND_IC:
                    return INDinitCond.Value;
                case _c.IND_CURRENT:
                    if (ckt.CurrentAnalysis.HasFlag(AnalysisType.DoingAc))
                    {
                        throw new AcCurrentPowerException();
                    }
                    else
                    {
                        return ckt.State.Solution[INDbrEq];
                    }
                case _c.IND_POWER:
                    if (ckt.CurrentAnalysis.HasFlag(AnalysisType.DoingAc))
                    {
                        throw new AcCurrentPowerException();
                    }
                    else
                    {
                        return ckt.State.Solution[INDbrEq] *
                                ckt.State.States[0][INDstate + INDvolt];
                    }
                default:
                    throw new BadParameterException();
            }
        }

        /// <summary>
        /// Set up the inductor
        /// </summary>
        /// <param name="ckt"></param>
        public override void Setup(Circuit ckt, Model model, object p = null)
        {
            CircuitNode[] nodes = BindNodes(ckt, NodeType.Current);
            INDposNode = nodes[0].Number;
            INDnegNode = nodes[1].Number;
            INDbrEq = nodes[2].Number;

            // Create 2 states
            INDstate = CreateState(ckt, 2);
        }

        /// <summary>
        /// Do temperature-dependent initialization
        /// </summary>
        /// <param name="ckt"></param>
        public override void Temperature(Circuit ckt, Model model, object p = null)
        {
            // Nothing to do here
        }

        /// <summary>
        /// Load the inductor in the circuit
        /// </summary>
        /// <param name="ckt"></param>
        public override void Load(Circuit ckt, Model model, object p = null)
        {
            CircuitState state = ckt.State;
            var result = new Integration.IntegrationMethod.IntegrationResult();
            int pass = (int)p;

            // First pass
            if (pass < 0 || pass == 0)
            {
                if ((ckt.Mode & (Circuit.Modes.Dc | Circuit.Modes.InitPred)) == 0)
                {
                    if (((ckt.Mode & Circuit.Modes.Uic) != 0) && ((ckt.Mode & Circuit.Modes.InitTran) != 0))
                    {
                        state.States[0][INDstate + INDflux] = INDinduct * INDinitCond;
                    }
                    else
                    {
                        state.States[0][INDstate + INDflux] = INDinduct * state.Solution[INDbrEq];
                    }
                }
            }

            // Second pass is for mutual inductors (if specified)
            if (pass == 1)
                return;

            // Third pass: Load the matrix
            if (pass < 0 || pass == 2)
            {
                if ((ckt.Mode & Circuit.Modes.Dc) != 0)
                {
                    // Integration result already initialized
                }
                else
                {
                    if ((ckt.Mode & Circuit.Modes.InitTran) != 0)
                    {
                        state.States[1][INDstate + INDflux] =
                                state.States[0][INDstate + INDflux];
                    }

                    result = ckt.Integration.Integrate(state, INDstate + INDflux, INDinduct);
                }
                state.Rhs[INDbrEq] += result.Ceq;
                if ((ckt.Mode & Circuit.Modes.InitTran) != 0)
                {
                    state.States[1][INDstate + INDvolt] =
                        state.States[0][INDstate + INDvolt];
                }
                state.Matrix[INDposNode, INDbrEq] += 1;
                state.Matrix[INDnegNode, INDbrEq] -= 1;
                state.Matrix[INDbrEq, INDposNode] += 1;
                state.Matrix[INDbrEq, INDnegNode] -= 1;
                state.Matrix[INDbrEq, INDbrEq] -= result.Geq;
            }
            
        }

        /// <summary>
        /// Load the inductor for AC analysis
        /// </summary>
        /// <param name="ckt"></param>
        public override void AcLoad(Circuit ckt, Model model, object p = null)
        {
            CircuitStateComplex state = ckt.StateComplex;

            Complex val = state.Omega * INDinduct.Value;
            if (INDposNode >= 0)
            {
                state.Matrix[INDposNode, INDbrEq] += 1.0;
                state.Matrix[INDbrEq, INDposNode] += 1.0;
            }
            if (INDnegNode >= 0)
            {
                state.Matrix[INDnegNode, INDbrEq] -= 1.0;
                state.Matrix[INDbrEq, INDnegNode] -= 1.0;
            }
            state.Matrix[INDbrEq, INDbrEq] -= val;
        }
    }
}
